#!/usr/bin/env python3
# SPDX-License-Identifier: MulanPSL-2.0+
# Copyright (c) 2025 Huawei Technologies Co., Ltd. All rights reserved.

"""
Job Resubmission Tool

用途：根据已有的 job_id 获取配置，应用参数覆盖后重新提交作业
典型场景：
- 使用相同的 commit 但不同的编译配置重新编译
- 验证编译环境的可重现性
- 快速测试参数变化的影响
"""

import argparse
import sys
import os
import yaml

# 添加 bisect 模块路径
sys.path.append(os.path.join(os.environ.get('LKP_SRC', ''), 'programs/bisect-py'))

from py_bisect import GitBisect
from bisect_log_config import logger


def apply_overrides(job: dict, overrides: list) -> dict:
    """
    应用参数覆盖到 job 配置

    Args:
        job: 原始 job 配置字典
        overrides: 覆盖参数列表，格式: ['key.path=value', ...]

    Returns:
        修改后的 job 配置

    Examples:
        >>> job = {'program': {'makepkg': {'commit': 'abc123'}}}
        >>> apply_overrides(job, ['program.makepkg.config=arm64-defconfig'])
    """
    if not overrides:
        return job

    for override in overrides:
        if '=' not in override:
            logger.warning(f"Invalid override format (missing '='): {override}")
            continue

        path, value = override.split('=', 1)
        keys = path.split('.')

        # 特殊处理：删除字段（value 为空）
        if not value:
            try:
                current = job
                for key in keys[:-1]:
                    if key not in current:
                        break
                    current = current[key]
                else:
                    if keys[-1] in current:
                        del current[keys[-1]]
                        logger.info(f"Deleted field: {path}")
                        continue
            except (KeyError, TypeError) as e:
                logger.warning(f"Failed to delete field {path}: {e}")
                continue

        # 正常情况：设置或更新字段值
        try:
            current = job
            # 导航到目标位置（创建不存在的中间层级）
            for key in keys[:-1]:
                if key not in current:
                    current[key] = {}
                elif not isinstance(current[key], dict):
                    logger.warning(f"Cannot navigate through non-dict at {key}")
                    break
                current = current[key]
            else:
                # 尝试解析 YAML 值（支持数字、布尔等类型）
                try:
                    parsed_value = yaml.safe_load(value)
                    current[keys[-1]] = parsed_value
                except yaml.YAMLError:
                    # 解析失败则作为字符串处理
                    current[keys[-1]] = value

                logger.info(f"Applied override: {path} = {current[keys[-1]]}")

        except Exception as e:
            logger.error(f"Failed to apply override {path}={value}: {e}")
            continue

    return job


def resubmit_job_by_id(gb: GitBisect, job_id: str, overrides: list = None,
                       output_yaml: str = None, force: bool = True,
                       dry_run: bool = False) -> tuple:
    """
    根据 job_id 获取配置，应用 overrides 后重新提交

    Args:
        gb: GitBisect 实例
        job_id: 原始 job 的 ID
        overrides: 覆盖参数列表，格式: ['key.path=value', ...]
        output_yaml: 可选，保存最终 job 配置到文件
        force: 是否强制提交（跳过去重检查），默认 True
        dry_run: 仅输出 YAML 而不提交任务，默认 False

    Returns:
        tuple: (new_job_id, result_root) 或 (None, None) 如果 dry_run=True

    Raises:
        ValueError: job_id 无效或找不到
        Exception: 提交失败
    """
    logger.info(f"Fetching configuration for job {job_id}...")

    # 1. 从数据库获取原始 job 配置并清理
    try:
        job_copy = gb.init_job_content(job_id)
    except Exception as e:
        logger.error(f"Failed to fetch job {job_id}: {e}")
        raise

    logger.info(f"Original job configuration retrieved (commit: {gb._get_commit_from_job(job_copy)})")

    # 2. 应用 overrides
    if overrides:
        logger.info(f"Applying {len(overrides)} overrides...")
        job_copy = apply_overrides(job_copy, overrides)

    # 3. 可选：保存最终配置到文件
    if output_yaml:
        try:
            with open(output_yaml, 'w') as f:
                yaml.dump(job_copy, f, sort_keys=False, default_flow_style=False)
            logger.info(f"Final job configuration saved to: {output_yaml}")
        except Exception as e:
            logger.warning(f"Failed to save job config: {e}")

    # 4. Dry-run 模式：只输出 YAML，不提交
    if dry_run:
        logger.info("Dry-run mode: Skipping job submission")
        if not output_yaml:
            # 如果没有指定输出文件，则打印到标准输出
            print("\n" + "="*60)
            print("Generated Job Configuration (YAML)")
            print("="*60)
            print(yaml.dump(job_copy, sort_keys=False, default_flow_style=False))
            print("="*60)
        return None, None

    # 5. 提交 job
    logger.info(f"Submitting new job (force={force})...")
    try:
        new_job_id, result_root = gb.submit_job(job_copy, force=force)
        logger.info(f"Job submitted successfully!")
        logger.info(f"  New Job ID: {new_job_id}")
        logger.info(f"  Result Root: {result_root}")
        return new_job_id, result_root
    except Exception as e:
        logger.error(f"Job submission failed: {e}")
        raise


def main():
    parser = argparse.ArgumentParser(
        description='根据已有 job_id 重新提交作业（支持参数覆盖）',
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog='''
使用示例：

1. 简单重新提交（使用相同配置）：
   $ ./resubmit_job.py --job-id 12345678

2. 更改编译配置重新提交：
   $ ./resubmit_job.py --job-id 12345678 \\
       --override program.makepkg.config=arm64-randconfig-005-20250721

3. 更改多个参数：
   $ ./resubmit_job.py --job-id 12345678 \\
       --override program.makepkg.config=defconfig \\
       --override program.makepkg.extra_args="W=2 C=1"

4. 删除某个参数：
   $ ./resubmit_job.py --job-id 12345678 \\
       --override program.makepkg.branch=

5. 保存最终配置到文件：
   $ ./resubmit_job.py --job-id 12345678 \\
       --override program.makepkg.config=defconfig \\
       --output-yaml final_job.yaml

6. 仅生成 YAML 配置而不提交（输出到标准输出）：
   $ ./resubmit_job.py --job-id 12345678 \\
       --override program.makepkg.config=defconfig \\
       --dry-run

7. 仅生成 YAML 配置并保存到文件（不提交）：
   $ ./resubmit_job.py --job-id 12345678 \\
       --override program.makepkg.config=defconfig \\
       --output-yaml test_job.yaml \\
       --dry-run

典型应用场景：
- 验证编译可重现性（相同 commit + 相同 config）
- 测试不同编译参数的影响
- 快速复现特定配置的问题
- 预览将要提交的任务配置（dry-run 模式）
        '''
    )

    parser.add_argument(
        '--job-id',
        required=True,
        help='原始 job 的 ID'
    )

    parser.add_argument(
        '--override',
        action='append',
        default=[],
        metavar='KEY=VALUE',
        help='覆盖参数（可多次使用），格式: key.path=value，留空值则删除字段'
    )

    parser.add_argument(
        '--output-yaml',
        metavar='FILE',
        help='保存最终 job 配置到 YAML 文件'
    )

    parser.add_argument(
        '--result-root',
        default='resubmit_results',
        help='结果存储目录（默认: resubmit_results）'
    )

    parser.add_argument(
        '--force',
        action='store_true',
        default=True,
        help='强制提交，跳过去重检查（默认: True）'
    )

    parser.add_argument(
        '--no-force',
        dest='force',
        action='store_false',
        help='允许去重检查，如果已存在相同配置的完成任务则不提交'
    )

    parser.add_argument(
        '--dry-run',
        action='store_true',
        help='仅生成 YAML 配置而不提交任务（可配合 --output-yaml 使用）'
    )

    args = parser.parse_args()

    # 初始化 GitBisect
    try:
        gb = GitBisect()
        gb.set_log(args.result_root)

        # 执行重新提交
        new_job_id, result_root = resubmit_job_by_id(
            gb=gb,
            job_id=args.job_id,
            overrides=args.override,
            output_yaml=args.output_yaml,
            force=args.force,
            dry_run=args.dry_run
        )

        # 输出结果摘要
        if args.dry_run:
            print("\n" + "="*60)
            print("Dry-run Mode: Job Configuration Generated")
            print("="*60)
            print(f"Original Job ID: {args.job_id}")
            if args.output_yaml:
                print(f"Configuration saved to: {args.output_yaml}")
            if args.override:
                print(f"\nApplied Overrides:")
                for override in args.override:
                    print(f"  - {override}")
            print("="*60)
            print("\nNo job was submitted (dry-run mode)")
        else:
            print("\n" + "="*60)
            print("Job Resubmission Summary")
            print("="*60)
            print(f"Original Job ID: {args.job_id}")
            print(f"New Job ID:      {new_job_id}")
            print(f"Result Root:     {result_root}")
            if args.override:
                print(f"\nApplied Overrides:")
                for override in args.override:
                    print(f"  - {override}")
            print("="*60)

        return 0

    except Exception as e:
        logger.exception("Job resubmission failed")
        print(f"\nError: {e}", file=sys.stderr)
        return 1


if __name__ == "__main__":
    sys.exit(main())
